import { fabric } from 'fabric';
import { isEmpty, merge, pick } from 'lodash-es';

import { generateLayerId } from '@/pages/TemplateEditor/constant';
import {
  LayerInfoBE,
  LayerInfoFE,
  LayerInfoFEData,
  LayerInfoFEDataImage,
  LayerRatioInfoBE
} from '@/pages/TemplateEditor/interface';
import {
  getImageBoundByCropped,
  getRotatedPositionByCustomPoint
} from '@/utils/fabricJs/cropImage/result';

import {
  getLayerTypeFEByBE,
  LayerSubTypeBE,
  LayerTypeBE,
  LayerTypeFE,
  mapSubTypeToBE,
  SHOPEE_FONT_DEFAULT
} from '../constant';
import { generateNewLayerName, hexToRgb, hexToRgba, rgbToHex } from './common';

const controlProperties = {
  cornerColor: '#fff',
  cornerStrokeColor: '#bfbfbf',
  borderColor: '#bfbfbf',
  transparentCorners: false
};

export const isImageObject = <O extends fabric.Object | LayerInfoFE>(
  obj: O
): obj is O & (O extends fabric.Object ? fabric.Image : LayerInfoFE) =>
  [LayerTypeBE.Image, LayerTypeBE.Product].includes(obj.data?.type);

export const isShapeObject = (obj: LayerInfoFE | fabric.Object): boolean =>
  [LayerTypeBE.Shape].includes(obj.data?.type);

export const isNotCroppedLayer = (
  obj: fabric.Object | LayerInfoFE
): boolean => {
  if (!isImageObject(obj)) {
    return false;
  }

  const { cropped, isCropped } = obj.data?.image || {};
  return !isCropped && isCroppedLayer(cropped);
};

export const isCroppedLayer = (
  cropped: [number, number, number, number]
): boolean => {
  if (isEmpty(cropped)) {
    return false;
  }

  const [top, right, bottom, left] = cropped;
  if (!top && !right && !bottom && !left) {
    return false;
  }

  return true;
};

export const transferToFELayersInfo = async (
  beLayers: LayerInfoBE[],
  layerInfo: LayerRatioInfoBE
): Promise<LayerInfoFE[]> => {
  return await Promise.all(
    (beLayers || []).map(async (layer) => {
      // 大小、位置属性
      const { width, height, x, y } =
        layer.type === LayerTypeBE.Product && layer.originImageInfo
          ? layer.originImageInfo
          : layer;
      const newLayer = { width, height } as LayerInfoFE;
      newLayer.left = x + newLayer.width / 2;
      newLayer.top = y + newLayer.height / 2;
      newLayer.angle = layer.rotation % 360;

      newLayer.type = getLayerTypeFEByBE(
        layer.type,
        layer.type === LayerTypeBE.Shape ? layer.graphics?.subtype : undefined
      );

      // 控制角属性
      merge(newLayer, controlProperties);

      // 业务属性存储于data字段
      newLayer.data = pick(layer, [
        'z',
        'id',
        'name',
        'type',
        'isHidden',
        'condition',
        'rotation'
      ]) as any;

      if (layer.type === LayerTypeBE.Shape && layer.graphics) {
        const beGraphics = layer.graphics;

        // 业务属性存储
        newLayer.data.graphics = pick(beGraphics, [
          'content',
          'fillColor',
          'transparency',
          'borderColor',
          'borderRadius',
          'borderOpacity'
        ]);

        // 填充颜色
        newLayer.fill = hexToRgba(
          beGraphics.fillColor,
          beGraphics.transparency
        );

        // 图形边框颜色
        newLayer.stroke = hexToRgba(
          beGraphics.borderColor,
          beGraphics.borderOpacity
        );

        // border radius
        if (beGraphics.subtype === LayerSubTypeBE.Ellipse) {
          newLayer.rx = newLayer.width / 2;
          newLayer.ry = newLayer.height / 2;
        } else if (beGraphics.subtype === LayerSubTypeBE.Triangular) {
          newLayer.radius = beGraphics.borderRadius;
        } else {
          newLayer.rx = beGraphics.borderRadius;
          newLayer.ry = beGraphics.borderRadius;
        }
      } else if (
        (layer.type === LayerTypeBE.Image ||
          layer.type === LayerTypeBE.Product) &&
        layer.image
      ) {
        const beImage = layer.image;

        // src获取，此时 beImage.link 是 fileKey
        newLayer.src = `/imagestop/design/preview/${beImage.link}`;

        newLayer.data.image = pick(beImage, [
          'name',
          'cropped'
        ]) as LayerInfoFEDataImage;
        newLayer.data.image.originSrc = newLayer.src;
        newLayer.data.image.fileKey = beImage.link; // 保存 fileKey

        if (layer.type === LayerTypeBE.Product) {
          newLayer.data.ratio = layerInfo[layer.id]?.ratio;
        }

        // 如果已经裁剪了，旋转中心应该为裁剪后的中心点坐标，因此需要修正中心点坐标
        if (!isEmpty(newLayer.data.image.cropped)) {
          const [topCropped, rightCropped, bottomCropped, leftCropped] =
            newLayer.data.image.cropped;

          // 先裁剪，获取裁剪之后的中心点坐标
          const croppedCenterX =
            x + (newLayer.width * (100 + leftCropped - rightCropped)) / 200;
          const croppedCenterY =
            y + (newLayer.height * (100 + topCropped - bottomCropped)) / 200;

          // 当前原图的中心点坐标需要绕裁剪后中心点坐标旋转
          const currentCenter = getRotatedPositionByCustomPoint(
            new fabric.Point(croppedCenterX, croppedCenterY),
            new fabric.Point(newLayer.left, newLayer.top),
            -newLayer.angle
          );

          // 设置正确的中心点坐标
          newLayer.left = currentCenter.x;
          newLayer.top = currentCenter.y;
        }
      } else if (layer.type === LayerTypeBE.Text && layer.text) {
        newLayer.src = ''; // 字体的src要real time preview后才显示出来

        const beText = layer.text;
        newLayer.data.text = pick(beText, [
          'content',
          'fontSize',
          'fontFamily',
          'textDecorationLine',
          'horizontalAlign',
          'verticalAlign',
          'borderRadius'
        ]) as any;

        // 处理文字颜色
        newLayer.data.text.textColor = rgbToHex(
          beText.fontColor[0],
          beText.fontColor[1],
          beText.fontColor[2]
        );
        // 处理填充颜色
        newLayer.data.text.fill = rgbToHex(
          beText.fillColor[0],
          beText.fillColor[1],
          beText.fillColor[2]
        );
        // 处理填充透明度
        newLayer.data.text.opacity =
          Math.round((beText.fillColor[3] / 255) * 100) / 100;
      }

      newLayer.opacity = getOpacityByData(newLayer.data);

      return newLayer;
    })
  );
};

export const floatToInt = (x): number => {
  return Math.round(Number.parseFloat(x));
};

// 将 base64 图片转换为 Blob 对象
function base64ToBlob(base64Data, contentType = ''): Blob {
  const pureBase64Data = base64Data.replace(
    /^data:image\/(png|jpeg|jpg);base64,/,
    ''
  );
  const byteCharacters = atob(pureBase64Data);
  const byteArrays = [];

  for (let offset = 0; offset < byteCharacters.length; offset += 512) {
    const slice = byteCharacters.slice(offset, offset + 512);

    const byteNumbers = new Array(slice.length);
    for (let i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    const byteArray = new Uint8Array(byteNumbers);
    byteArrays.push(byteArray);
  }

  return new Blob(byteArrays, { type: contentType });
}

export const getShapeImageKey = async (
  objects // screen界面上真实渲染的objects，用于获取graphics的content
): Promise<Array<{ fileKey: string; id: string }>> =>
  await Promise.all(
    objects
      .filter((obj) => obj.data.type === LayerTypeBE.Shape)
      .map(async (obj) => {
        const formData = new FormData();
        const blobData = base64ToBlob(obj.data.graphics.content);
        formData.append('file', blobData);
        formData.append('image_type', 'cp_layer');
        const fileKey = Math.random();

        return {
          fileKey,
          id: obj.data.id
        };
      })
  );

export const transferToBELayersInfo = (
  feLayers: LayerInfoFE[] = [],
  shapeKeys: Array<{ fileKey: string; id: string }> = []
): LayerInfoBE[] => {
  // eslint-disable-next-line cognitive-complexity/cognitive-complexity-forced
  return feLayers.map((layer) => {
    const newLayer = pick(layer.data, ['z', 'id', 'name', 'type']) as any;

    newLayer.width = floatToInt(layer.width);
    newLayer.height = floatToInt(layer.height);
    const layerPoints = getNoAnglePoints(layer);
    newLayer.x = floatToInt(layerPoints.leftTop.x);
    newLayer.y = floatToInt(layerPoints.leftTop.y);
    newLayer.rotation = floatToInt(layer.data.rotation);
    newLayer.isHidden = layer.data.isHidden;

    if (newLayer.type === LayerTypeBE.Shape) {
      const { borderRadius } = layer.data.graphics;
      const object = shapeKeys.find((obj) => obj.id === layer.data.id);

      newLayer.graphics = {
        subtype: mapSubTypeToBE[layer.type],
        content: object ? object.fileKey : layer.data.graphics.content,
        borderRadius: floatToInt(borderRadius),
        ...pick(layer.data.graphics, [
          'fillColor',
          'transparency',
          'borderColor',
          'borderOpacity'
        ])
      };
    } else if (
      newLayer.type === LayerTypeBE.Image ||
      newLayer.type === LayerTypeBE.Product
    ) {
      const { name, cropped = [], isCropped, fileKey } = layer.data.image || {};
      newLayer.image = {
        name,
        cropped: isCropped ? cropped : [0, 0, 0, 0],
        link: fileKey
      };
      if (isCropped) {
        const { width: originImageWidth, height: originImageHeight } =
          getImageBoundByCropped(layer, cropped as any);
        const points = getNoAnglePoints(layer);
        const topCropped = cropped[0];
        const leftCropped = cropped[3];

        const originImageLeft =
          points.leftTop.x - (originImageWidth * leftCropped) / 100;
        const originImageTop =
          points.leftTop.y - (originImageHeight * topCropped) / 100;

        if (newLayer.type === LayerTypeBE.Image) {
          newLayer.width = floatToInt(originImageWidth);
          newLayer.height = floatToInt(originImageHeight);
          newLayer.x = floatToInt(originImageLeft);
          newLayer.y = floatToInt(originImageTop);
        } else if (newLayer.type === LayerTypeBE.Product) {
          // Product layer 需要给后端传layer的 width、height，原图的存起来自己用
          newLayer.originImageInfo = {
            width: floatToInt(originImageWidth),
            height: floatToInt(originImageHeight),
            x: floatToInt(originImageLeft),
            y: floatToInt(originImageTop)
          };
        }
      }
    } else if (newLayer.type === LayerTypeBE.Text) {
      newLayer.text = pick(layer.data.text, [
        'fontSize',
        'fontFamily',
        'content',
        'textDecorationLine',
        'horizontalAlign',
        'verticalAlign'
      ]);

      const { textColor, fill, opacity, borderRadius } = layer.data.text;
      const fontColor = hexToRgb(textColor || '');
      newLayer.text.fontColor = [fontColor.r, fontColor.g, fontColor.b, 255];
      newLayer.text.borderRadius = floatToInt(borderRadius);

      const fillColor = hexToRgb(fill || '');
      newLayer.text.fillColor = [
        fillColor.r,
        fillColor.g,
        fillColor.b,
        +parseInt(opacity * 255 + '', 10)
      ];
    }

    return newLayer;
  });
};

export const getBELayerRatioInfo = (
  feLayers: LayerInfoFE[] = []
): LayerRatioInfoBE => {
  const result = {};

  feLayers
    .filter((layer) => layer.data.type === LayerTypeBE.Product)
    .forEach((layer) => {
      result[layer.data.id] = {
        ratio: layer.data.ratio || [1, 1]
      };
    });

  return result;
};

export const getInitialLayerData = (
  type: LayerTypeBE,
  currentLayers: LayerInfoFE[],
  screenSize: string
): LayerInfoFE => {
  const commonData = getInitialCommonLayerData(
    type,
    generateLayerId(type),
    generateNewLayerName(type, currentLayers),
    currentLayers.length || 0
  );
  switch (type) {
    case LayerTypeBE.Image:
      return getInitialImageLayerData(commonData);
    case LayerTypeBE.Product:
      return getInitialProductImageLayerData(commonData);
    case LayerTypeBE.Shape:
      return getInitialShapeLayerData(commonData);
    case LayerTypeBE.Text:
      return getInitialTextLayerData(commonData, screenSize);
    default:
      return commonData;
  }
};

export const getInitialCommonLayerData = (
  beType: LayerTypeBE,
  id: string,
  name: string,
  zIndex: number
): LayerInfoFE => {
  return {
    top: 100,
    left: 100,
    angle: 0,
    width: 200,
    height: 200,
    ...controlProperties,
    type: getLayerTypeFEByBE(beType, LayerSubTypeBE.Rectangular),
    data: {
      id,
      name,
      z: zIndex,
      type: beType,
      isHidden: false,
      rotation: 0
    }
  };
};

export const getInitialImageLayerData = (
  commonData: LayerInfoFE
): LayerInfoFE => {
  return {
    ...commonData,
    left: 0,
    top: 0,
    width: 0,
    height: 0,
    opacity: 0
  };
};

export const getInitialProductImageLayerData = (
  commonData: LayerInfoFE
): LayerInfoFE => {
  return {
    ...commonData,
    left: 0,
    top: 0,
    width: 0,
    height: 0,
    opacity: 0,
    data: {
      ...commonData.data,
      ratio: [1, 1]
    }
  };
};

export const getInitialTextLayerData = (
  commonData: LayerInfoFE,
  screenSize: string
): LayerInfoFE => {
  const [width] = screenSize.split('x').map(Number);

  return {
    ...commonData,
    left: width / 2,
    top: 100,
    width,
    height: 200,
    data: {
      ...commonData.data,
      needPreview: true,
      text: {
        opacity: 0,
        content: '',
        fontSize: 40,
        borderRadius: 0,
        fill: '#ffffff',
        fontFamily: SHOPEE_FONT_DEFAULT, // 字体类型
        textColor: '#000000', // 文字颜色
        textDecorationLine: '', // 下划线 删除线
        horizontalAlign: 'left', // 文字水平靠左 居中 靠右
        verticalAlign: 'center'
      }
    }
  };
};

export const getInitialShapeLayerData = (
  commonData: LayerInfoFE
): LayerInfoFE => {
  return {
    ...commonData,
    type: LayerTypeFE.Rect,
    fill: '#666666',
    stroke: '#666666',
    strokeWidth: 2,
    data: {
      ...commonData.data,
      graphics: {
        content: '',
        fillColor: '#666666',
        transparency: 1,
        borderRadius: 0,
        borderOpacity: 1,
        borderColor: '#666666'
      }
    }
  };
};

/**
 * 获取当前layer的opacity值
 * 1. 如果隐藏，判断isHidden
 * 2. 如果展示，则设为1
 */
export const getOpacityByData = (data: LayerInfoFEData): number => {
  if (data.isHidden) {
    return 0;
  }

  return 1;
};

// 获取当前layer的坐标点，均为摆正时的，不考虑角度
export const getNoAnglePoints = (
  info:
    | {
        width?: number;
        height?: number;
        left?: number;
        top?: number;
        scaleX?: number;
        scaleY?: number;
      }
    | fabric.Object,
  centerPoint?: fabric.Point
): {
  center: fabric.Point;
  leftTop: fabric.Point;
  rightBottom: fabric.Point;
} => {
  const width =
    'getScaledWidth' in info
      ? info.getScaledWidth()
      : info.width * (info.scaleX || 1);
  const height =
    'getScaledHeight' in info
      ? info.getScaledHeight()
      : info.height * (info.scaleY || 1);

  const widthBy2 = width / 2;
  const heightBy2 = height / 2;

  const center = centerPoint
    ? centerPoint
    : new fabric.Point(info.left, info.top);

  const leftTop = new fabric.Point(center.x - widthBy2, center.y - heightBy2);

  const rightBottom = new fabric.Point(
    center.x + widthBy2,
    center.y + heightBy2
  );

  return { center, leftTop, rightBottom };
};
